package com.sunfeng.springcloud.service;

import cn.hutool.core.util.IdUtil;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.concurrent.TimeUnit;

/**
 * @author sunfeng
 *
 *      处理分布式系统延迟和容错的开源库
 *      服务降级 fallback  假设对方服务不可用了 至少返回一个兜底的解决方法，即向服务调用方返回一个符合预期的，可处理的相应
 *
 *
 *      服务熔断
 *      服务限流
 *
 * @date 2021/04/01 16:06
 **/
@Slf4j
@Service
public class PaymentService {

    /**
     * 正常访问
     *
     * @param id
     * @return
     */
    public String paymentInfo_OK(Integer id) {

        return "线程池:  " + Thread.currentThread().getName() + "  paymentInfo_OK,id:  " + id + "\t" + "O(∩_∩)O哈哈~";
    }

    /**
     * 超时访问
     * 服务提供方的服务降级,服务消费者也需要设置服务降级的处理保护
     * 设置8001自身调用超时时间的峰值，峰值内可以正常运行，超过了需要有兜底的方法处理，作为服务降级fallback
     * 方法就是当在规定的5秒内没有完成，那么就会触发服务降级，返回一个兜底的解决方案
     * 同时不仅是超时，假设服务内的方法出现了异常，也同样会触发兜底的解决方法，
     *  execution.isolation.thread.timeoutInMilliseconds
     * @param id
     * @return
     */
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler"/*指定善后方法名*/,
         commandProperties = {@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "5000")})
    public String paymentInfo_TimeOut(Integer id) {
//        int x= 10/0;
        try {
            TimeUnit.MILLISECONDS.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:  " + Thread.currentThread().getName() + " id:  " + id + "\t" + "O(∩_∩)O哈哈~" + "  耗时(秒): 3";
    }

    /**
     * 服务端调用超时    会调用这个方法
     *   服务出错也会走这个方法
     *
     * @param id
     * @return
     */
    public String paymentInfo_TimeOutHandler(Integer id) {
        return "线程池:  " + Thread.currentThread().getName() + "  8001系统繁忙或者运行报错，请稍后再试,id:  " + id + "\t" + "o(╥﹏╥)o";
    }
    //====服务熔断，上方是降级

    /**
     *
     *
     * 在10秒窗口期中10次请求有6次是请求失败的,断路器将起作用
     * 必须满足总阈值
     *  关闭状态: 断路器关闭，流量可以正常进入
     *  打开/熔断状态: 断路器打开,即circuit-breaker 熔断状态,拒绝所有流量走降级逻辑
     *  半开状态:  断路器的半开状态，Open状态过一段时间（默认5秒） 转为此状态尝试恢复.此状态时:此状态时允许有且仅有一个请求进入，一旦请求成功就i关闭断路器。请求失败就进入Open状态（再过5秒进入半开状态）
     * @param id
     * @return
     */
    @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
            // 是否开启断路器
            @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
            //请求总阈值  在快照时间窗口内，必须满足请求总数阈值才有资格熔断。默认为20，意味着在10秒内，如果hystrix的调用总次数不足20次，即使所有请求都超时或者其他原因失败，断路器都不会打开。
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),

            // 时间窗口期 熔断后经过多久恢复一次尝试，默认为最近的10秒。   如果成功 则取消熔断
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),
            //错误百分比阈值：当请求总数在快照时间窗内超过了阈值，比如发生了30次调用，并且有15次发生了超时异常，也就是超过了50的错误百分比，在默认设定的50%阈值情况下，这时候就会将断路器打开
            // 也就是说在20秒内有十次错误断路器打开
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
    })
    public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
        // 如果发生熔断 这个方法也不会执行
        log.info("进入服务方法paymentCircuitBreaker");
        if (id < 0) {
            throw new RuntimeException("******id 不能负数");
        }
        String serialNumber = IdUtil.simpleUUID();

        return Thread.currentThread().getName() + "\t" + "调用成功，流水号: " + serialNumber;
    }

    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
        return "id 不能负数，请稍后再试，/(ㄒoㄒ)/~~   id: " + id;
    }

}
